/*
 * Copyright (c) 2023, Phytium Technology Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <ipmid/api-types.hpp>
#include <ipmid/handler.hpp>
#include <ipmid/types.hpp>
#include <phosphor-logging/log.hpp>

#include <fstream>
#include <iomanip>
#include <string>

namespace ipmi
{
constexpr Cmd cmdRecordHostStat = 0x30;

typedef enum tagComponentId
{
    CPU = 0x11,
    MEMORY = 0x22,
    NETINTERFACE = 0x33,
    DISK = 0x44,
    SYSINFO = 0x55,
} ComponentId;

using namespace phosphor::logging;
const char* filePath = "/usr/share/host_resource_status.txt";

inline std::string getCompName(const ComponentId compId)
{
    switch (compId)
    {
        case CPU:
            return "CPU";
        case MEMORY:
            return "Memory";
        case NETINTERFACE:
            return "NetInterface";
        case DISK:
            return "Disk";
        case SYSINFO:
            return "SysInfo";
    }
    return "Unknown";
}

inline bool checkCompId(const ComponentId compId)
{
    ComponentId ids[] = {CPU, MEMORY, NETINTERFACE, DISK, SYSINFO};

    for (size_t i = 0; i < sizeof(ids) / sizeof(ids[0]); i++)
    {
        if (compId == ids[i])
        {
            return true;
        }
    }
    return false;
}

/*
 * The fuction implements recording the usage of the Host's components.
 */
ipmi::RspType<> ipmiOEMRecordHostStat(const std::vector<uint8_t>& reqData)
{
    const int timeLen = 20;
    auto getTime = [](std::string& timeStr) {
        time_t curTime = time(nullptr);
        struct tm* pTm = localtime(&curTime);
        strftime(timeStr.data(), timeLen, "%F %T", pTm);
        return;
    };
    int len = reqData.size();
    /* check if the validity of the request */
    if (len < 3)
    {
        log<level::ERR>("The length of request is too short");
        return ipmi::responseReqDataLenInvalid();
    }

    /* parse the content of the requst */
    ComponentId compId = static_cast<ComponentId>(reqData.front());
    int usageInt, usageFract;

    if (checkCompId(compId) == false)
    {
        return ipmi::responseInvalidFieldRequest();
    }
    /* the parsed data is writen to one file in the below forms
     *
     *             2023-07-06 10:58:53 CPU 64.00%
     *             2023-07-06 10:58:54 Memory 84.02% capacity
     */
    std::ofstream outFs(filePath, std::ios::out | std::ios::app);
    std::string timeStr(timeLen - 1, 0);
    getTime(timeStr);
    std::string compName(getCompName(compId));
    auto printStr = [&outFs, &reqData](int& pos, int end) {
        while (pos < end)
        {
            char ch = static_cast<char>(reqData[pos]);
            if (ch == 0)
            {
                pos = end;
                break;
            }
            outFs << ch;
            pos++;
        }
    };

    /* write to a file */
    outFs << timeStr << " " << compName;
    switch (compId)
    {
        case SYSINFO:
        {
            const int hostName = 13;
            const int osName = 25;
            const int version = 33;
            int pos, end;
            int sysInfoName[] = {1, hostName, osName, version};

            /* print the string such as 'HostName OsName KernelVersion
             * MemoryCapacity' */
            pos = sysInfoName[0];
            for (size_t i = 1; i < sizeof(sysInfoName) / sizeof(sysInfoName[0]);
                 i++)
            {
                outFs << " ";
                end = sysInfoName[i];
                printStr(pos, end);
            }

            unsigned short total = reqData[len - 2] << 8 | reqData[len - 1];
            outFs << " " << total;
            break;
        }
        case NETINTERFACE:
        {
            const int nicName = 17;
            int i = 1;

            outFs << " ";
            printStr(i, nicName);

            while (i < len - 3)
            {
                usageInt = reqData[i++];
                usageFract = reqData[i++];
                outFs << " " << usageInt << ".";
                outFs << std::setfill('0') << std::setw(2) << usageFract << " ";
            }

            unsigned short maxSpeed = reqData[len - 3] << 8 | reqData[len - 2];

            outFs << maxSpeed << " ";
            if (reqData[len - 1] != 0)
            {
                outFs << reqData[len - 1];
            }
            outFs << "bps";
            break;
        }
        case DISK:
        {
            const int diskName = 33;
            int i = 1;

            outFs << " ";
            printStr(i, diskName);

            /* usage percent */
            usageInt = static_cast<int>(reqData[i++]);
            usageFract = static_cast<int>(reqData[i++]);
            outFs << " " << usageInt << ".";
            outFs << std::setfill('0') << std::setw(2) << usageFract << " ";

            /* Total Capacity, the last two characters note as unit. */
            int limit = len - 2;

            while (i < len - 2)
            {
                unsigned short size = reqData[i++] << 8;
                size |= reqData[i++];
                outFs << size << " " << static_cast<char>(reqData[limit++])
                      << "B";
                if (limit != len)
                {
                    outFs << " ";
                }
            }
            break;
        }
        case MEMORY:
        {
            for (int i = 1; i < len; i++)
            {
                usageInt = reqData[i++];
                usageFract = reqData[i++];
                int capacity = reqData[i++] << 8;
                capacity |= reqData[i++];
                outFs << " " << usageInt << ".";
                outFs << std::setfill('0') << std::setw(2) << usageFract << " ";
                outFs << capacity;
            }
            break;
        }
        default:
            for (int i = 1; i < len;)
            {
                usageInt = reqData[i++];
                usageFract = reqData[i++];
                outFs << " " << usageInt << ".";
                outFs << std::setfill('0') << std::setw(2) << usageFract;
            }
    }
    outFs << std::endl;
    outFs.close();
    return ipmi::responseSuccess();
}

/* register ome command to a handler */
static void registerOEMFunctions() __attribute__((constructor));

static void registerOEMFunctions(void)
{
    log<level::INFO>("Registering OEM commands");

    ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnOemSeven,
                          cmdRecordHostStat, ipmi::Privilege::User,
                          ipmiOEMRecordHostStat);
}
} // namespace ipmi
